//  R2.java
//
//  Author:
//       Juan J. Durillo <juanjo.durillo@gmail.com>
//
//  Copyright (c) 2013 Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
package jmetal.qualityIndicator;

import jmetal.core.SolutionSet;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.StringTokenizer;

public class R2 {

    public jmetal.qualityIndicator.util.MetricsUtil utils_;
    double[][] matrix_ = null;
    double[][] lambda_ = null;
    int nObj_ = 0;

    /**
     * Constructor Creates a new instance of the R2 indicator for a problem with
     * two objectives and 100 lambda vectors
     */
    public R2() {
        utils_ = new jmetal.qualityIndicator.util.MetricsUtil();
        //*****IMPORTANTE*****
        // by default it creates an R2 indicator for a two dimensions probllem and
        // uses only 100 weight vectors for the R2 computation
        nObj_ = 2;
        // generating the weights
        lambda_ = new double[100][2];
        for (int n = 0; n < 100; n++) {
            double a = 1.0 * n / (100 - 1);
            lambda_[n][0] = a;
            lambda_[n][1] = 1 - a;
        } // for

    } // R2

    /**
     * Constructor Creates a new instance of the R2 indicator for a problem with
     * two objectives and N lambda vectors
     */
    public R2(int nVectors) {
        utils_ = new jmetal.qualityIndicator.util.MetricsUtil();

        // by default it creates an R2 indicator for a two dimensions probllem and
        // uses only <code>nVectors</code> weight vectors for the R2 computation
        nObj_ = 2;
        // generating the weights
        lambda_ = new double[nVectors][2];
        for (int n = 0; n < nVectors; n++) {
            double a = 1.0 * n / (nVectors - 1);
            lambda_[n][0] = a;
            lambda_[n][1] = 1 - a;
        } // for

    } // R2

    /**
     * Constructor Creates a new instance of the R2 indicator for nDimensiosn It
     * loads the weight vectors from the file fileName
     */
    public R2(int nObj, String file) {
        utils_ = new jmetal.qualityIndicator.util.MetricsUtil();
    // A file is indicated, the weights are taken from there

        // by default it creates an R2 indicator for a two dimensions probllem and
        // uses only <code>nVectors</code> weight vectors for the R2 computation
        nObj_ = nObj;
    // generating the weights

        // reading weights
        try {
            // Open the file
            FileInputStream fis = new FileInputStream(file);
            InputStreamReader isr = new InputStreamReader(fis);
            BufferedReader br = new BufferedReader(isr);

            int numberOfObjectives = 0;
            int i = 0;
            int j = 0;
            String aux = br.readLine();
            LinkedList<double[]> list = new LinkedList<double[]>();
            while (aux != null) {
                StringTokenizer st = new StringTokenizer(aux);
                j = 0;
                numberOfObjectives = st.countTokens();
                double[] vector = new double[nObj];
                while (st.hasMoreTokens()) {
                    double value = new Double(st.nextToken());
                    vector[j++] = value;
                }
                list.add(vector);
                aux = br.readLine();
            }
            br.close();

            // convert the LinkedList into a vector
            lambda_ = new double[list.size()][];
            int index = 0;
            for (double[] aList : list) {
                lambda_[index++] = aList;
            }

        } catch (Exception e) {
            System.out.println("initUniformWeight: failed when reading for file: " + file);
            e.printStackTrace();
        }
    } // R2

    /**
     * Returns the R2 indicator value of a given front
     *
     * @param front The front
     * @param trueParetoFront The true Pareto front
     * @param numberOfObjectives The number of objectives
     * @param lambda A vector containing the lambda vectors for R2
     */
    public double R2Withouth(double[][] approximation, double[][] paretoFront, int index) {

        /**
         * Stores the maximum values of true Pareto front.
         */
        double[] maximumValue;

        /**
         * Stores the minimum values of the true Pareto front.
         */
        double[] minimumValue;

        /**
         * Stores the normalized front.
         */
        double[][] normalizedApproximation;

        /**
         * Stores the normalized true Pareto front.
         */
        double[][] normalizedParetoFront;

        // STEP 1. Obtain the maximum and minimum values of the Pareto front
        maximumValue = utils_.getMaximumValues(paretoFront, nObj_);
        minimumValue = utils_.getMinimumValues(paretoFront, nObj_);

        // STEP 2. Get the normalized front and true Pareto fronts
        normalizedApproximation = utils_.getNormalizedFront(approximation,
                maximumValue,
                minimumValue);
        normalizedParetoFront = utils_.getNormalizedFront(paretoFront,
                maximumValue,
                minimumValue);

        // STEP 3. compute all the matrix of tchebicheff values if it is null
        //if (matrix_ == null) {
        matrix_ = new double[approximation.length][lambda_.length];
        for (int i = 0; i < approximation.length; i++) {
            for (int j = 0; j < lambda_.length; j++) {
                matrix_[i][j] = lambda_[j][0] * Math.abs(normalizedApproximation[i][0]);
                for (int n = 1; n < nObj_; n++) {
                    matrix_[i][j] = Math.max(matrix_[i][j], lambda_[j][n] * Math.abs(normalizedApproximation[i][n]));
                }
            }
        }
    //}

        // STEP45. Compute the R2 value withouth the point
        double sumWithout = 0.0;
        for (int i = 0; i < lambda_.length; i++) {

            double tmp;
            if (index != 0) {
                tmp = matrix_[0][i];
            } else {
                tmp = matrix_[1][i];
            }
            for (int j = 0; j < approximation.length; j++) {
                if (j != index) {
                    tmp = Math.min(tmp, matrix_[j][i]);
                }
            }
            sumWithout += tmp;
        }

        // STEP 5. Return the R2 value
        return sumWithout / (double) lambda_.length;

    } // R2

    /**
     * Returns the element contributing the most to the R2 indicator
     *
     * @param front The front
     * @param trueParetoFront The true Pareto front
     * @param numberOfObjectives The number of objectives
     * @param lambda A vector containing the lambda vectors for R2
     */
    public int getBest(double[][] approximation, double[][] paretoFront) {
        int index_best = -1;
        double value = Double.NEGATIVE_INFINITY;

        for (int i = 0; i < approximation.length; i++) {
            double aux = this.R2Withouth(approximation, paretoFront, i);
            if (aux > value) {
                index_best = i;
                value = aux;
            }
        }

        return index_best;
    } // getBest 

    /**
     * Returns the element contributing the less to the R2
     *
     * @param front The front
     * @param trueParetoFront The true Pareto front
     * @param numberOfObjectives The number of objectives
     * @param lambda A vector containing the lambda vectors for R2
     */
    public int getWorst(double[][] approximation, double[][] paretoFront) {
        int index_worst = -1;
        double value = Double.POSITIVE_INFINITY;

        for (int i = 0; i < approximation.length; i++) {
            double aux = this.R2Withouth(approximation, paretoFront, i);
            if (aux < value) {
                index_worst = i;
                value = aux;
            }
        }

        return index_worst;
    } // getWorst

    /**
     * Returns the element contributing the most to the R2
     *
     * @param front A solution set
     */
    public int getBest(SolutionSet set) {
        double[][] approximationFront = set.writeObjectivesToMatrix();
        double[][] trueFront = set.writeObjectivesToMatrix();

        return this.getBest(approximationFront, trueFront);
    } // getWorst

    /**
     * Returns the element contributing the less to the R2
     *
     * @param front The front
     * @param trueParetoFront The true Pareto front
     */
    public int getWorst(SolutionSet set) {
        double[][] approximationFront = set.writeObjectivesToMatrix();
        double[][] trueFront = set.writeObjectivesToMatrix();

        return this.getWorst(approximationFront, trueFront);
    } // getWorst

    /**
     * Returns the element contributing the most to the R2 indicator
     *
     * @param front The front
     * @param trueParetoFront The true Pareto front
     * @param numberOfObjectives The number of objectives
     * @param lambda A vector containing the lambda vectors for R2
     */
    public int[] getNBest(double[][] approximation, double[][] paretoFront, int N) {
        int[] index_bests = new int[approximation.length];
        double[] values = new double[approximation.length];

        for (int i = 0; i < approximation.length; i++) {
            values[i] = this.R2Withouth(approximation, paretoFront, i);
            index_bests[i] = i;
        } // for

        // sorting the values and index_bests
        for (int i = 0; i < approximation.length; i++) {
            for (int j = i; j < approximation.length; j++) {
                if (values[j] < values[i]) {
                    double aux = values[j];
                    values[j] = values[i];
                    values[i] = aux;

                    int aux_index = index_bests[j];
                    index_bests[j] = index_bests[i];
                    index_bests[i] = aux_index;
                }
            }
        }

        int[] res = new int[N];
        for (int i = 0; i < N; i++) {
            res[i] = index_bests[i];
        }

        return res;
    } // getBest

    /**
     * Returns the indexes of the N best solutions according to this indicator
     *
     * @param SolutionSet the solution set for which the best solutions are
     * computed
     */
    public int[] getNBest(SolutionSet set, int N) {
        double[][] approximationFront = set.writeObjectivesToMatrix();
        double[][] trueFront = set.writeObjectivesToMatrix();

        return this.getNBest(approximationFront, trueFront, N);

    } // getNBest

    /**
     * Returns the R2 indicator value of a given front
     *
     * @param front The front
     * @param trueParetoFront The true Pareto front
     * @param numberOfObjectives The number of objectives
     * @param lambda A vector containing the lambda vectors for R2
     */
    public double R2(double[][] approximation, double[][] paretoFront) {
        /**
         * Stores the maximum values of true pareto front.
         */
        double[] maximumValue;

        /**
         * Stores the minimum values of the true pareto front.
         */
        double[] minimumValue;

        /**
         * Stores the normalized front.
         */
        double[][] normalizedApproximation;

        /**
         * Stores the normalized true Pareto front.
         */
        double[][] normalizedParetoFront;

        // STEP 1. Obtain the maximum and minimum values of the Pareto front
        maximumValue = utils_.getMaximumValues(paretoFront, nObj_);
        minimumValue = utils_.getMinimumValues(paretoFront, nObj_);

        // STEP 2. Get the normalized front and true Pareto fronts
        normalizedApproximation = utils_.getNormalizedFront(approximation, maximumValue, minimumValue);
        normalizedParetoFront = utils_.getNormalizedFront(paretoFront, maximumValue, minimumValue);

        // STEP 3. compute all the matrix of tchebicheff values if it is null
        //if (matrix_ == null) {
        matrix_ = new double[approximation.length][lambda_.length];
        for (int i = 0; i < approximation.length; i++) {
            for (int j = 0; j < lambda_.length; j++) {
                matrix_[i][j] = lambda_[j][0] * Math.abs(normalizedApproximation[i][0]);
                for (int n = 1; n < nObj_; n++) {
                    matrix_[i][j] = Math.max(matrix_[i][j], lambda_[j][n] * Math.abs(normalizedApproximation[i][n]));
                }
            }
        }
    //}

        // STEP 4. The matrix is not null. Compute the R2 value
        double sum = 0.0;
        for (int i = 0; i < lambda_.length; i++) {
            double tmp = matrix_[0][i];
            for (int j = 1; j < approximation.length; j++) {
                tmp = Math.min(tmp, matrix_[j][i]);
            }
            sum += tmp;
        }

        // STEP 5. Return the R2 value
        return sum / (double) lambda_.length;

    } // R2

    /**
     * Returns the R2 indicator of a given population, using as a reference
     * point 0, 0. Normalization is using taking into account the population
     * itself
     *
     * @param set
     * @return
     */
    public double R2(SolutionSet set) {
        double[][] approximationFront = set.writeObjectivesToMatrix();
        double[][] trueFront = set.writeObjectivesToMatrix();

        return this.R2(approximationFront, trueFront);

    } // R2

    /**
     * Returns the R2 indicator value of a given front
     *
     * @param front The front
     * @param trueParetoFront The true Pareto front
     * @param numberOfObjectives The number of objectives
     * @param lambda A vector containing the lambda vectors for R2
     */
    public double R2Without(SolutionSet set, int index) {
        double[][] approximationFront = set.writeObjectivesToMatrix();
        double[][] trueFront = set.writeObjectivesToMatrix();
        return this.R2(approximationFront, trueFront);
    } // R2ContributionWithout

    /**
     * This class can be call from the command line. At least three parameters
     * are required: 1) the name of the file containing the front, 2) the number
     * of objectives 2) a file containing the reference point / the Optimal
     * Pareto front for normalizing 3) the file containing the weight vector
     */
    public static void main(String args[]) {
        if (args.length < 3) {
            System.err.println("Error using R2. Usage: \n java jmetal.qualityIndicator.Hypervolume "
                    + "<SolutionFrontFile> "
                    + "<TrueFrontFile> " + "<getNumberOfObjectives>");
            System.exit(1);
        }

        //Create a new instance of the metric
        R2 qualityIndicator;
        //Read the front from the files

        int nObj = new Integer(args[2]);

        if (nObj == 2 && args.length == 3) {
            qualityIndicator = new R2();

        } else {
            qualityIndicator = new R2(nObj, args[3]);
        }

        double[][] approximationFront = qualityIndicator.utils_.readFront(args[0]);

        double[][] paretoFront = qualityIndicator.utils_.readFront(args[1]);

        //Obtain delta value
        double value = qualityIndicator.R2(approximationFront, paretoFront);

        System.out.println(value);

        System.out.println(qualityIndicator.R2Withouth(approximationFront, paretoFront, 1));
        System.out.println(qualityIndicator.R2Withouth(approximationFront, paretoFront, 15));
        System.out.println(qualityIndicator.R2Withouth(approximationFront, paretoFront, 25));
        System.out.println(qualityIndicator.R2Withouth(approximationFront, paretoFront, 75));

    } // main
} // R2
